package de.bensoft.jrail.client.com.interfaces;

import javax.inject.Inject;

import org.apache.log4j.Logger;

import de.bensoft.jrail.client.com.GlobalCommunicator;
import de.bensoft.jrail.client.com.SerialCommunicator;

public class LI100 {

	private static final Logger log = Logger.getLogger(LI100.class);
	
	// EXT
	@Inject
	GlobalCommunicator globalComm;
	
	@Inject
	SerialCommunicator scomm;
	
	// **************************************************************************************
	// FROM COMMAND STATION
	// **************************************************************************************
	private static final byte 	STATION_HEADER_UNREQUESTED_INFORMATION = 0x61;
	
	private static final byte[] STATION_COMMAND_EMERGENCY_STOP 	= new byte[] { (byte)0x81, 0x00, (byte)0x81 };
	
	private static final byte[] STATION_COMMAND_TRACKPOWER_OFF	= new byte[] { 0x61, 0x00, 0x61 };
	private static final byte[] STATION_COMMAND_RESUME			= new byte[] { 0x61, 0x01, 0x60 };
	
	// **************************************************************************************
	// FROM INTERFACE
	// **************************************************************************************
	public static final byte[] INTERFACE_COMMAND_RESUME 		= new byte[] { 0x21, (byte)0x81, (byte)0xA0 };	
	public static final byte[] INTERFACE_COMMAND_EMERGENCY_OFF 	= new byte[] { 0x21, (byte)0x80, (byte)0xA1 };
	
	
	// Internal buffer for receiving commands
	private byte[] commandbuffer;
	
	private boolean isReceivingCommand;
	private int     commandLength;
	private int		expectedCommandLength;
	
	public LI100() {
		commandbuffer 		= new byte[8];
		isReceivingCommand 	= false;
	}
	
	/**
	 * Handles incoming data
	 * @param data
	 * @param size
	 */
	public void incoming(byte[] data, int offset, int size) {
		
		// Still receiving ...
		if(isReceivingCommand) {			
			parseIncoming(data, size);
			
		// New Command
		} else {
			commandLength 		= 0;
			
 			if(data[offset] == STATION_HEADER_UNREQUESTED_INFORMATION) {
				expectedCommandLength = 3;
				isReceivingCommand 	= true;
				parseIncoming(data, size);
			}
		}
		
	}
	
	private void parseIncoming(byte[] data, int size) {
		
		if(commandLength + size >= commandbuffer.length) {
			log.error("There is a problem by receiving incoming data => " + getCurrentCommandString(commandbuffer, commandbuffer.length));
			isReceivingCommand = false;
		} else {		
			
			boolean rest = false;
			int toread	 = size;
			
			if(commandLength + toread > expectedCommandLength) {
				toread = expectedCommandLength - commandLength;
				rest   = true;
			}
			
			int readed = 0;
			for(int i=0; i < toread; i++) {
				commandbuffer[commandLength + i] = data[i];
				readed++;
			}
			commandLength+=readed;
			
			if(commandLength == expectedCommandLength) {
				byte[] cmd = new byte[commandLength];
				for(int i=0; i < commandLength; i++) {
					cmd[i] = commandbuffer[i];
				}
				interpretCommand(cmd);
				isReceivingCommand = false;
			}
			
			if(rest) {
				incoming(data, readed, size - readed);
			}
			
		}
		
	}

	private void interpretCommand(byte[] cmd) {	
		switch(cmd[0]) {	
			case STATION_HEADER_UNREQUESTED_INFORMATION:
				
				if(verifySimpleCommand(cmd, STATION_COMMAND_TRACKPOWER_OFF)) {
					log.info("Track power off received");
					globalComm.trackPowerProperty().set(false);
				} else if(verifySimpleCommand(cmd, STATION_COMMAND_RESUME)) {
					log.info("Normal operation resumed received");
					globalComm.trackPowerProperty().set(true);
				} else {
					log.warn("Unable to map command => " + getCurrentCommandString(cmd, cmd.length));
				}
				
			break;
		}
	}
	
	private boolean verifySimpleCommand(byte[] command, byte[] proof) {
		
		if(proof.length != command.length)
			return false;
		
		for(int i=0; i < command.length; i++) {
			if(command[i] != proof[i])
				return false;
		}
			
		return true;
	}
	
	private String getCurrentCommandString(byte[] cmd, int size) {
		String ret = "";
		for(int i=0; i < size; i++) {
			ret += String.format("0x%02x", cmd[i]) + " ";
		}
		return ret;
	}
	
	/**
	 * Send a command to the LI100 interface
	 * @param cmd
	 */
	public void sendCommand(byte[] cmd) {
		scomm.sendData(cmd);
	}
	
	/**
	 * TrainSpeedCommand for the LI100 
	 * @param steps
	 * @param address
	 * @param forward
	 * @param value
	 * @return
	 */
	public static byte[] buildTrainSpeedCommand(long steps, long address, boolean forward, long value) {
		byte[] cmd = new byte[6];
		
		if(steps == 28l) {
			cmd[0] = (byte)0xE4; // Header
			cmd[1] = (byte)0x12; // Identification
			
			cmd[2] = (byte)0x00; 				// ADR HIGH
			cmd[3] = (byte)(address & 0xFF); 	// ADR LOW
			
			// Speed
			if(value > steps)
				value = steps;
			
			if(value != 0)
				value += 4;
			
			byte speed = (byte)(((value & 0x01) << 4) | ((value >> 1) & 0xF));
			
			// Directions
			byte fw = 0x00;
			if(forward)
				fw = 0x01;
			
			// Collect it
			byte b4 = (byte)(((fw << 7) & 0x80) | (speed & 0x1F));	
			cmd[4] = b4;
			
			// Proof
			cmd[5] = (byte)(cmd[0] ^ cmd[1] ^ cmd[2] ^ cmd[3] ^ cmd[4]);				
		}else if(steps == 128l) {
			cmd[0] = (byte)0xE4; // Header
			cmd[1] = (byte)0x13; // Identification
			
			cmd[2] = (byte)0x00; 				// ADR HIGH
			cmd[3] = (byte)(address & 0xFF); 	// ADR LOW
			
			// Speed
			if(value > 126)
				value = 126;
			
			if(value != 0)
				value += 2;
			
			byte speed = (byte)(value & 0x7F);
			
			// Directions
			byte fw = 0x00;
			if(forward)
				fw = 0x01;
						
			cmd[4] = (byte)(((fw << 7) & 0x80) | (speed & 0x7F));

			// Proof
			cmd[5] = (byte)(cmd[0] ^ cmd[1] ^ cmd[2] ^ cmd[3] ^ cmd[4]);	
		}
	
		return cmd;
	}
	
	
	public static byte[] buildFunctionStateCommandGroup1(long address, boolean S0, boolean S1, boolean S2, boolean S3, boolean S4) {
		byte[] cmd = new byte[6];
		
		cmd[0] = (byte)0xE4; // Header
		cmd[1] = (byte)0x20; // Ident
		
		cmd[2] = (byte)0x00; 			// AddressHigh
		cmd[3] = (byte)(address & 0xFF);  // AddressLow
		
		
		byte b4 = 0x00;
		
		if(S0)
			b4 |= ((1 << 4) & 0x10);
		if(S1)
			b4 |= (1 & 0x01);
		if(S2)
			b4 |= ((1 << 1) & 0x02);
		if(S3)
			b4 |= ((1 << 2) & 0x04);
		if(S4)
			b4 |= ((1 << 3) & 0x08);
		
		cmd[4] = b4;
		
		cmd[5] = (byte)(cmd[0] ^ cmd[1] ^ cmd[2] ^ cmd[3] ^ cmd[4]);
		
		return cmd;
	}
	
	public static byte[] buildSwitchCommand(long address, boolean straight) {
		
		byte[] cmd = new byte[4];
		
		if(address > 0)
			address-=1;
		
		int adr_real = (int)(address / 4);
		int offset   = (int)address - ((int)(address / 4) * 4);
		

		
		cmd[0] = (byte)0x52; 				// Header
		cmd[1] = (byte)(adr_real & 0xFF);   // Adress
		
		byte on = 0x01;
		if(!straight)
			on = 0x00;
		
		cmd[2] = (byte)((0x08 << 4) | (0x01 << 3) | ((offset & 0x03) << 1) | (on & 0x01));
		
		cmd[3] = (byte)(cmd[0] ^ cmd[1] ^ cmd[2]);
		
		return cmd;
	}
	
}
